package com.pan.download5.sync;

import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 分析多线程应该要把握关键，应该同步的是多线程共享的且会互相影响的东西
 * 应该要把真正要同步的东西提取出来，
 *
 * 这个锁是在解锁的同时通过CAS维护一个阻塞章节线程的Map，使得在解锁的时候同时将该章节线程阻塞，同时提供了外部方法根据章节id唤醒相应的线程
 * Author: Pān Qí-ān
 * 作者：潘祺安
 */
public class CasUnlockAndBlock implements UnlockAndBlock {
    private ReentrantLock lock = new ReentrantLock();
    /**
     * 感觉这个许可证本质上不是保证线程阻塞唤醒本身的同步的，因为线程本身的阻塞和唤醒本身不是多共享状态
     * 这里多线程共享的是是否当前线程是否阻塞的状态，应该要同步的是threadMap这个变量的操作
     *
     */
    private AtomicInteger permit = new AtomicInteger(1);
    private HashMap<Integer,Thread> threadMap = new HashMap<>();

    private ThreadPoolExecutor unlockThreadPool;

    public CasUnlockAndBlock() {
        unlockThreadPool = new ThreadPoolExecutor(1,1,1145, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(1));
    }

    @Override
    public void lock() {
        lock.lock();
        //确认当前没有线程正在进行阻塞或唤醒操作，如果有就，就在这自旋等待对方操作完成完成，再放行
        while (!permit.compareAndSet(1,1));
//        因为这里并没有涉及对threadMap的操作，因此这里并不需要获得许可，只需要等待对方完成，
//        在外部看来，解锁和阻塞仍然是一个原子操作，因为阻塞完成前外部其实就算拿到了内部的锁，也必须自旋到其他线程完成阻塞才能继续

    }

    @Override
    public void unlock() {
        lock.unlock();
    }

    /**
     * 这里要保证解锁的同时线程进入阻塞，实际在外部可以看成锁释放后，但还没阻塞前，别的线程无法或锁（指外部）
     * @param id
     */
    @Override
    public void unlockAndBlock(int id) {
        //自旋虽然响应快，但对cpu的开销十分大，因此因该确保cas自旋修饰的同步代码足够的轻量和可靠，不然cas自旋可能得不偿失
        while (!permit.compareAndSet(1,0));
        lock.unlock();//释放锁
        Thread blockThread = Thread.currentThread();
        threadMap.put(id,blockThread);
        unlockThreadPool.execute(()->{
            for (;LockSupport.getBlocker(blockThread) != permit;);//等待LockSupport.park(permit);被执行
            for (;!permit.compareAndSet(0,1););//释放许可
        });
        LockSupport.park(permit);//进入阻塞
    }

    @Override
    public void wakeBlockThread(int id) {
        while (!permit.compareAndSet(1,0));
        if (threadMap.containsKey(id)) {
//            HashMap.remove操作对系统的开销还是挺大的，而且该项目如果执行该id任务的线程被阻塞起来了，
//            有且仅有一次唤醒的次数，id任务对应的线程信息也不会被再次使用，不删除问题应该也不是太大
//            Thread blockThread = threadMap.get(id);
//            threadMap.remove(id);
//            threadMap.remove(id);
//            我又研究了一下，Integer的hashCode直接是用value值的，而章节任务的id（也就是key值）表现为从0开始的，差值为1的数列，
//            因此hashMap中 hash = int ^ int >>> 16在int小于65536且大于0的情况下（一本小说通常也也没那么多章节）hash 等于int本身
//            因此在这里threadMap的key在哈希函数是完美的均匀的分布在哈希表桶中的（而且就算大于65536也是均匀分布的），因此这里HashMap.remove(key)这了的时间复杂度为 O(1) 开销不大
//            相反，如果不进行删除操作，空间复杂度为O(n), 综合考虑还是remove比较好
            Thread blockThread = threadMap.remove(id);
            LockSupport.unpark(blockThread);
        }
        while (!permit.compareAndSet(0,1));
    }

    @Override
    public Thread getBlockerById(int id) {
        while (!permit.compareAndSet(1,0));
        try {
            return threadMap.containsKey(id)?
                 threadMap.remove(id):null;
        }finally {
            while (!permit.compareAndSet(0,1));
        }
    }

    @Override
    public void close() {
        unlockThreadPool.shutdown();
    }
}
